package com.ubi.core.swagger.reader;

import com.ubi.core.framework.request.model.RequestModel;
import com.mangofactory.swagger.configuration.SwaggerGlobalSettings;
import com.mangofactory.swagger.core.CommandExecutor;
import com.mangofactory.swagger.models.alternates.AlternateTypeProvider;
import com.mangofactory.swagger.models.dto.AllowableValues;
import com.mangofactory.swagger.models.dto.Parameter;
import com.mangofactory.swagger.readers.Command;
import com.mangofactory.swagger.readers.operation.HandlerMethodResolver;
import com.mangofactory.swagger.readers.operation.RequestMappingReader;
import com.mangofactory.swagger.readers.operation.ResolvedMethodParameter;
import com.mangofactory.swagger.readers.operation.parameter.ParameterAllowableReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterDataTypeReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterDefaultReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterDescriptionReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterMultiplesReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterNameReader;
import com.mangofactory.swagger.readers.operation.parameter.ParameterRequiredReader;
import com.mangofactory.swagger.scanners.RequestMappingContext;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.method.HandlerMethod;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.collect.Lists.newArrayList;

/**
 * 只吃自定义注解@RequestModel的使用
 *
 * @author leiyi
 * @date 2016-04-26 15:51
 * @summary:RequestModelSwaggerReader
 */
public class RequestModelSwaggerReader implements RequestMappingReader {

    @Override
    public void execute(RequestMappingContext context) {
        List<Parameter> parameters = newArrayList();
        parameters.addAll(this.readParameters(context));
        // 直接覆盖？单独针对
        context.put("parameters", parameters);
    }

    protected Collection<? extends Parameter> readParameters(RequestMappingContext context) {
        HandlerMethod handlerMethod = context.getHandlerMethod();
        SwaggerGlobalSettings swaggerGlobalSettings = (SwaggerGlobalSettings) context.get("swaggerGlobalSettings");
        HandlerMethodResolver handlerMethodResolver = new HandlerMethodResolver(swaggerGlobalSettings.getTypeResolver());
        AlternateTypeProvider alternateTypeProvider = swaggerGlobalSettings.getAlternateTypeProvider();

        List<ResolvedMethodParameter> methodParameters = handlerMethodResolver.methodParameters(handlerMethod);
        List<Parameter> parameters = newArrayList();

        List<Command<RequestMappingContext>> commandList = newArrayList();
        commandList.add(new ParameterAllowableReader());
        commandList.add(new ParameterDataTypeReader());
        commandList.add(new CustomParameterTypeReader());
        commandList.add(new ParameterDefaultReader());
        commandList.add(new ParameterDescriptionReader());
        commandList.add(new ParameterMultiplesReader());
        commandList.add(new ParameterNameReader());
        commandList.add(new ParameterRequiredReader());

        RequestModelParameterExpander expander = new RequestModelParameterExpander(alternateTypeProvider);
        for (ResolvedMethodParameter methodParameter : methodParameters) {

            if (!shouldIgnore(methodParameter, swaggerGlobalSettings.getIgnorableParameterTypes())) {
                RequestMappingContext parameterContext = new RequestMappingContext(context.getRequestMappingInfo(), handlerMethod);
                parameterContext.put("methodParameter", methodParameter.getMethodParameter());
                parameterContext.put("resolvedMethodParameter", methodParameter);
                parameterContext.put("swaggerGlobalSettings", swaggerGlobalSettings);
                CommandExecutor<Map<String, Object>, RequestMappingContext> commandExecutor = new CommandExecutor<>();
                commandExecutor.execute(commandList, parameterContext);
                Map<String, Object> result = parameterContext.getResult();

                if (!shouldExpand(methodParameter)) {
                    Parameter parameter = new com.mangofactory.swagger.models.dto.builder.ParameterBuilder().name((String)
                            result.get("name")).description((String) result.get("description")).defaultValue((String) result
                            .get("defaultValue")).required((Boolean) result.get("required")).allowMultiple((Boolean) result.
                            get("allowMultiple")).dataType((String) result.get("dataType")).allowableValues((AllowableValues)
                            result.get("allowableValues")).parameterType((String) result.get("paramType")).parameterAccess(
                            (String) result.get("paramAccess")).build();
                    parameters.add(parameter);
                } else {
                    expander.expand("", methodParameter.getResolvedParameterType().getErasedType(), parameters);
                }
            }
        }
        return parameters;
    }

    private boolean shouldIgnore(final ResolvedMethodParameter parameter, final Set<Class> ignorableParamTypes) {
        if (null != ignorableParamTypes && !ignorableParamTypes.isEmpty()) {

            if (ignorableParamTypes.contains(parameter.getMethodParameter().getParameterType())) {
                return true;
            }
            for (Annotation annotation : parameter.getMethodParameter().getParameterAnnotations()) {
                if (ignorableParamTypes.contains(annotation.annotationType())) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean shouldExpand(final ResolvedMethodParameter parameter) {
        for (Annotation annotation : parameter.getMethodParameter().getParameterAnnotations()) {
            if (RequestModel.class == annotation.annotationType()) {
                return true;
            } else if (ModelAttribute.class == annotation.annotationType()) {
                return true;
            }
        }
        return false;
    }

}
